﻿using System;
using System.Drawing;
using WiMo.Games.Drawables;
using System.Collections.Generic;

namespace WiMo.Games.Premium.GDIDisplay
{
    public class DisplayManager : IDisplay
    {
        #region Fields
        IntPtr _hwnd;
        List<IDrawable> _items;
        List<IDrawable> _previousItems;
        Size _originalSize;        
        int _currentZIndex;
        RenderTree _renderTree;
        bool _isSceneDirty = true;
        WiMoGraphics _graphics;
        #endregion

        #region Constructor
        public DisplayManager(IntPtr hwnd, Size targetSize, Size size, byte aspectRatio)
        {
            SetDPI();
            Size = size;
            TargetSize = targetSize;

            _originalSize = size;

            _hwnd = hwnd;
            BackgroundColor = Color.Black;
            AspectRatio = aspectRatio;
            Size = size;
            
            SetAspectRatio(aspectRatio);
            DisplayStrategy = DisplayStrategies.Automatic;
        }
        #endregion
       
        #region Private Utility Methods
        private void SetDPI()
        {
            var bitmap = new Bitmap(64, 64);
            var gr = System.Drawing.Graphics.FromImage(bitmap);
            DPI = Convert.ToInt32(gr.DpiX);
            gr.Dispose();
            bitmap.Dispose();
        }

        private void SetAspectRatio(byte aspectRatio)
        {
            var widthRatio = Convert.ToSingle(Size.Width) / Convert.ToSingle(TargetSize.Width);
            var heightRatio = Convert.ToSingle(Size.Height) / Convert.ToSingle(TargetSize.Height);

            _renderTree = RenderTree.Create(Size, 3);
            _graphics = new WiMoGraphics(Size, _hwnd);

            switch (aspectRatio)
            {
                case WiMo.Games.Display.AspetRatioOneByOne:
                case WiMo.Games.Display.AspetRatioFourByThree:
                case WiMo.Games.Display.AspetRatioThreeByFour:
                    var ratio = Math.Min(heightRatio, widthRatio);
                    ScaleFactor = new Vector2(ratio, ratio);

                    if (widthRatio != heightRatio)
                    {
                        if (widthRatio < heightRatio)
                            DisplayOffset = new Location(0, (Size.Height - (TargetSize.Height * ratio)) / 2);
                        else
                            DisplayOffset = new Location((Size.Width - (TargetSize.Width * ratio)) / 2, 0);
                    }
                    break;
                default:
                    ScaleFactor = new Vector2(widthRatio, heightRatio);
                    break;
            }
        }
        
        public static Point[] GetPointArray(List<Location> locations, Point offset)
        {
            Point[] points = new Point[locations.Count];
            for (int i = 0; i < locations.Count; i++)
                points[i] = new Point(locations[i].X + offset.X, locations[i].Y + offset.Y);

            return points;
        }
        #endregion

        #region Public Utility Methods
        public void Add(IDrawable drawable)
        {
            if (DisplayStrategy == DisplayStrategies.MostlyStatic)
                _renderTree.AddSprite(drawable);

            drawable.ZIndex = _currentZIndex;
            _currentZIndex++;
            _items.Add(drawable);
        }

        public void Reset()
        {
            _graphics.Reset();

            _previousItems = _items;
            _items = new List<IDrawable>();
            _currentZIndex = 0;
        }

        public void Invalidate()
        {
            _isSceneDirty = true;
        }
        #endregion

        #region Render (Main)
        private void RenderBackground()
        {
            if (BackgroundImage != null)
            {
                if (_isSceneDirty || DisplayStrategy != DisplayStrategies.MostlyStatic || Diagnostics)
                    _graphics.DrawImage(BackgroundImage.TextureInformation as Bitmap, DisplayOffset.X, DisplayOffset.Y);
            }
            else
            {
                if (_isSceneDirty || DisplayStrategy != DisplayStrategies.MostlyStatic || Diagnostics)
                    _graphics.Clear(BackgroundColor);
            }
        }
        
        public void Render()
        {
            var allTimer = System.Diagnostics.Stopwatch.StartNew();

            var timer = System.Diagnostics.Stopwatch.StartNew();
            timer.Start();

            RenderBackground();

            var fillTime = timer.ElapsedMilliseconds;

            if (DisplayStrategy == DisplayStrategies.MostlyStatic)
            {
                if (_previousItems != null)
                {
                    foreach (var item in _previousItems)
                    {
                        if (!_items.Contains(item))
                            _renderTree.RemoveSprite(item);
                    }
                }
            }

            timer.Reset();
            timer.Start();

            _graphics.RenderBackground();

            var dirtyRects = 0;
            if (_isSceneDirty || DisplayStrategy != DisplayStrategies.MostlyStatic || Diagnostics)
            {
                foreach (var sprite in _items)
                    sprite.Render(this);

                if (Diagnostics)
                    _renderTree.RenderDirtyBlocks(_graphics);
            }
            else
                _renderTree.RenderChanges(this, _graphics);

            long listTime = timer.ElapsedMilliseconds;
            timer.Reset();
            timer.Start();

            _isSceneDirty = false;
            _renderTree.MarkAsClean();

            _graphics.RenderBackbuffer(_hwnd);
            var flipTime = timer.ElapsedMilliseconds;
            var debugMessage = string.Format("Total: {0:000} Render Background: {1:000} List {2:000} Flip Time {3:000} Dirty Rects {4}", allTimer.ElapsedMilliseconds, fillTime, listTime, flipTime, dirtyRects);
            //System.Diagnostics.Debug.WriteLine(debugMessage);
        }

        #endregion 

        #region Render Different Sprite Types
        #region Render Image
        public void Render(ISpriteImage sprite)        
        {
            throw new Exception("Render Shape should not be called directly.  Rendering should happen within the shape.");
        }
        #endregion

        #region Render Font
        public void Render(ISpriteText text)
        {
            throw new Exception("Render Shape should not be called directly.  Rendering should happen within the shape.");
        }
        #endregion

        #region Render Shape
        public Point GetAdjustedPosition(IPositionable shape)
        {
            int left = shape.Position.Point.X;
            int top = shape.Position.Point.Y;
            if (shape.Origin == Origin.Center)
            {
                left = Convert.ToInt32(left - shape.Size.Width / 2);
                top = Convert.ToInt32(top - shape.Size.Height / 2);
            }

            left += DisplayOffset.X;
            top += DisplayOffset.Y;

            return new Point(left, top);
        }
        
        public void Render(ISpriteShape shape)
        {
            throw new Exception("Render Shape should not be called directly.  Rendering should happen within the shape.");
        }
        #endregion

        #region Render Line
        public void Render(ISpriteLine line)
        {
            _graphics.DrawLine(line.LineColor, line.Width, line.Start, line.End);
        }
        #endregion
        #endregion
         
        #region Resize
        public void Resize(Size bounds)
        {
            _graphics.Clear();
        }
        #endregion

        #region Factory Methods
        public ISpriteImage CreateSpriteImage()
        {
            return new Drawables.SpriteImage();
        }

        public ISpriteShape CreateRectangle()
        {
            return new Drawables.Rectangle();
        }

        public ISpriteText CreateText()
        {
            return new Drawables.SpriteText();
        }

        public ISpriteLine CreateLine()
        {
            return new Drawables.Line();
        }

        public ISpriteShape CreateEllipse()
        {
            return new Drawables.Ellipse();
        }

        public ISpriteShape CreatePolygon()
        {
            return new Drawables.Polygon();
        }

        public ISpriteShape CreateLineList()
        {
            return new Drawables.LineList();
        }
        #endregion

        #region Properties
        public DisplayStrategies DisplayStrategy { get; set; }
        public int DPI { get; set; }
        public Vector2 ScaleFactor { get; private set; }
        public byte AspectRatio { get; set; }
        public Location DisplayOffset { get; private set; }
        public Size Size { get; private set; }
        public Size TargetSize { get; private set; }

        public bool Diagnostics { get; set; }
        private Color _backgroundColor;
        public Color BackgroundColor
        {
            get { return _backgroundColor; }
            set
            {
                if (_backgroundColor != value)
                {
                    Invalidate();
                    _backgroundColor = value;
                }
            }
        }
        public ITexture _backgroundImage;
        public ITexture BackgroundImage
        {
            get { return _backgroundImage; }
            set
            {
                if (_backgroundImage != value)
                {
                    _backgroundImage = value;
                    Invalidate();
                }
            }
        }

        internal IWiMoGraphics Graphics
        {
            get { return _graphics; }
        }

        public string Name
        {
            get { return "GDI - Premium"; }
        }
        #endregion        

        #region Premium render engine does not currently support adding items to the screen ad-hoc
        public ISpriteLine DrawLine(Location start, Location end, Color color, int width)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteText DrawText(PointF position, string message, ISpriteFont font, Color foreColor)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteText DrawText(PointF position, string message, ISpriteFont font, Color foreColor, Origin origin)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteShape DrawRectangle(Rectangle rect, Color color, int width)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteShape DrawRectangle(Rectangle rect, Color color, int width, Origin origin)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteShape DrawCircle(PointF position, int radius, Color borderColor, int width)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteShape FillRectangle(Rectangle rect, Color fillColor, Color borderColor, int width)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteShape FillRectangle(Rectangle rect, Color fillColor, Color borderColor, int width, Origin origin)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteShape FillCircle(PointF position, int radius, Color fillColor, Color borderColor, int width)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }
        #endregion

        #region IDisposable Members
        public void Dispose()
        {
            if (_graphics != null)
                _graphics.Dispose();
        }
        #endregion
    }
}
